#!/usr/bin/env python
#
# CUBudget: http://code.google.com/p/cubudget
#
# Copyright (c) 2008 Clemson University
# Released under the GNU GPL, version 2.

''' CUBudget Core
'''

from xml.dom.minidom import parse, Document
from xml.dom import Node
from xml.parsers.expat import ExpatError
import random, sys

# 'globals' for account types
# to use in function 'global EXPENSE'
EXPENSE = '1'
ASSET   = '2'
INCOME  = '3'

class Core(object):
   ''' Class for the core object
       Houses the API which other parts
       of the project rely on
   '''

   def __init__(self):
      self.DomXML      = None  # Dom Representation of xml
      self.dirty       = 0     # dirty bit. 0 for unchanged, 1 for changed
      self.accountList = []    # List to keep track of accounts

   def loadFile(self, location):
      ''' Loads an XML file for accounting data
          Returns appropriate error if encountered
      '''

      try:
         self.DomXML = parse(location)
         self.dirty = 0
      except IOError:
         return IOError
      except ExpatError:
         return ExpatError

      # Load account info into accountList
      accountNodeList = self.DomXML.getElementsByTagName('account')

      for account in accountNodeList:
         newAccountObj = Account(account.attributes.getNamedItem('name').value)
         newAccountObj.xmlNode = account
         #TODO - Load transaction data into account object
         self.accountList.append(newAccountObj)

   def saveFile(self, location):
      ''' Saves the XML document to location
          Returns IOError if encountered
      '''

      try:
         fp = open(location, "w")
         fp.write(self.DomXML.toprettyxml())
         fp.close()
         self.dirty = 0
      except IOError:
         return IOError

   def addAccount(self, name, accountType):
      ''' Adds a new account
          Updates the XML document to reflect the change
          Returns boolean of success
      '''

      # Create DOM object if doesn't exist
      if self.DomXML == None:
         self.DomXML = Document()
         firstChild = self.DomXML.createElement('cubudget')
         self.DomXML.appendChild(firstChild)

      # Create account element
      newAccount = self.findAccount(name)
      if newAccount != None and newAccount.type == accountType:
         return False

      newAccount = self.DomXML.createElement('account')

      # Set account attributes
      newAccount.setAttribute('name', name)
      newAccount.setAttribute('type', str(accountType))
      newAccount.setAttribute('balance', "0")

      # Append account to XML, update dirty bit
      self.DomXML.firstChild.appendChild(newAccount)
      self.dirty = 1
      return True

   def deleteAccount(self, name):
      ''' Deletes an existing account
          Updates the XML document to reflect the change
          Returns boolean of success
      '''

      account = self.findAccount(name)
      if account == None:
         return False

      account.parentNode.removeChild(account)
      self.dirty = 1
      return True

   def getAllTransactions(self, accountName):
      ''' Returns a list of all Transactions in an account
          Returns None if account doesn't exist
      '''

      account = self.findAccount(accountName)
      if account == None:
         return None

      nodeList = account.childNodes
      varList = [ ]
      objList = [ ]
      for transactions in nodeList:
         if transactions is not None:
            child = transactions.childNodes
            varList = [ ]
            for childTrans in child:
               if childTrans.nodeType == Node.ELEMENT_NODE:
                  varList.append(childTrans.childNodes[0].data)
         if varList != [ ]:
            appendTrans = Transaction(sourceAccount=varList[2],
                                      destinationAccount=varList[3],
                                      amount=varList[1],
                                      notes=varList[4], date=varList[0])
            appendTrans.uid = varList[5]
            objList.append(appendTrans)

      return objList

   def getAllAccounts(self):
      ''' Returns a list of all Account Names
          Returns None if no accounts exist
      '''

      if(self.DomXML is None):
         return None

      accountNodeList = self.DomXML.getElementsByTagName('account')
      accountList = [ ]
      for account in accountNodeList:
         accountList.append(account.attributes.getNamedItem('name').value)

      if accountList == [ ]:
         return None

      return accountList

   def getAccountBalance(self, accountName):
      ''' Returns balance of specified account
          Returns None if account not found
      '''
      account = self.findAccount(accountName)
      if account != None:
         return account.attributes.getNamedItem('balance').value
      else:
         return None

   def addTransaction(self, transaction):
      ''' Adds a transaction from source account to destination
          Updates the XML document to reflect the change
          Returns boolean value of success
      '''

      # Check to see if both accounts exist
      # currently commented out for the sake of still
      # using the examples when this file is run
      source = self.findAccount(transaction.source)
      destination = self.findAccount(transaction.destination)
      if source == None or destination == None:
         return False

      newTransaction = self.DomXML.createElement('transaction')

      # Put items in elements
      source = self.DomXML.createElement('source')
      sourceTxt = self.DomXML.createTextNode(transaction.source)
      source.appendChild(sourceTxt)

      destination = self.DomXML.createElement('destination')
      destTxt = self.DomXML.createTextNode(transaction.destination)
      destination.appendChild(destTxt)

      datetime = self.DomXML.createElement('datetime')
      dateTxt = self.DomXML.createTextNode(transaction.date)
      datetime.appendChild(dateTxt)

      amount = self.DomXML.createElement('amount')
      amountTxt = self.DomXML.createTextNode(transaction.amount)
      amount.appendChild(amountTxt)

      notes = self.DomXML.createElement('notes')
      notesTxt = self.DomXML.createTextNode(transaction.notes)
      notes.appendChild(notesTxt)

      uid = self.DomXML.createElement('uid')
      uidStr = self.generateUID()
      transaction.uid = uidStr
      uidTxt = self.DomXML.createTextNode(uidStr)
      uid.appendChild(uidTxt)

      # Append children to the newTransaction
      newTransaction.appendChild(source)
      newTransaction.appendChild(destination)
      newTransaction.appendChild(datetime)
      newTransaction.appendChild(amount)
      newTransaction.appendChild(notes)
      newTransaction.appendChild(uid)

      # FIXME - Currently appends to only source account
      # Needs to do both source and destination for double entry
      # Will possibly require modifying amount (+/-)
      account = self.findAccount(transaction.source)
      account.appendChild(newTransaction)

      self.__updateBalance(transaction.source, 0, transaction.amount)

      self.dirty = 1
      return True

   def __updateBalance(self, accountName, oldAmount, newAmount):
      ''' Updates the balance of the account
          Called on transaction changes
      '''
      account = self.findAccount(accountName)
      balance = float(account.attributes.getNamedItem('balance').value)
      balance -= float(oldAmount)
      balance += float(newAmount)
      account.setAttribute('balance', str(balance))

   def deleteTransaction(self, transaction):
      ''' Removes a transaction based on uid
          Returns boolean value of success
      '''

      delTrans = self.findTransaction(transaction.uid)
      # Not found
      if delTrans == None:
         return False

      accountName = delTrans.parentNode.attributes.getNamedItem('name').value
      oldAmount = delTrans.childNodes[3].firstChild.data
      self.__updateBalance(accountName, oldAmount, 0)

      # Delete Node
      delTrans.parentNode.removeChild(delTrans) # Delete node from DOM
      self.dirty = 1
      return True

   def updateTransaction(self, transaction):
      ''' Updates a transaction
          Returns boolean of success
      '''

      updateTrans = self.findTransaction(transaction.uid)
      # Not found
      if updateTrans == None:
         return False

      accountName = updateTrans.parentNode.attributes.getNamedItem('name').value
      oldAmount = updateTrans.childNodes[3].firstChild.data
      self.__updateBalance(accountName, oldAmount, 0)

      updateTrans.uid = transaction.uid
      # There has got to be a better way to do this
      updateTrans.parentNode.removeChild(updateTrans)

      self.addTransaction(transaction)
      self.dirty = 1
      return True

   def checkDirty(self):
      ''' Returns the current status of the dirty bit
          0 for unchanged, 1 for changed
      '''

      return self.dirty

   def findAccount(self, accountName):
      ''' Find account in current DomXML
          Returns account node on success, None if not found
      '''

      # No accounts or open XML, so nothing to find
      if self.DomXML is None:
         return None

      # Get account nodes
      accountNodeList = self.DomXML.getElementsByTagName('account')
      for account in accountNodeList:
         currName = account.attributes.getNamedItem('name').value
         if currName == accountName:
            return account

      # Account not found return None
      return None

   def findTransaction(self, uid):
      ''' Find transaction in current DomXML
          Returns transaction node on success, None if not found
      '''

      # Get Transaction nodes
      transactionNodeList = self.DomXML.getElementsByTagName('transaction')

      for aTransaction in transactionNodeList:
         if aTransaction is not None:
            theUID = aTransaction.getElementsByTagName('uid')
            theUID = theUID.item(0).firstChild.data
         if theUID == uid:
            return aTransaction

      # Transaction not found, return None
      return None

   def generateUID(self):
      ''' Generates a pseudo-random float based on system time
          However unlikely, the existence of the uid is checked
          to make sure it is not a duplicate
          Returns a string UID
      '''
      
      newUID = str(int(random.random() * 1000000000000))
      # Check UID and loop until it is unique
      while self.checkUID(newUID) == 1:
         newUID = int(random.random() * 1000000000000)

      return newUID

   def checkUID(self, uid):
      ''' Checks all transactions to see if UID is in use
          Returns 1 for in use and 0 for not in use
      '''

      for currUid in self.DomXML.getElementsByTagName('uid'):
         if currUid.firstChild.nodeValue == uid:
            # UID is in use return 1
            return 1

      # UID is not in use return 0   
      return 0

class Transaction(object):
   ''' Transaction object type for accounts
       Stores information regarding a particular transaction
   '''

   def __init__(self, sourceAccount, destinationAccount, amount, notes, date):
      self.source      = sourceAccount
      self.destination = destinationAccount
      self.amount      = amount
      self.notes       = notes
      self.date        = date     # MM/DD/YYYY format
      self.uid         = None

class Account(object):
   ''' Account object type
       Stores information regarding a particular account
   '''

   def __init__(self, accountName):
      self.name         = accountName
      self.transactions = [ ]
      self.xmlNode      = None;        # 'Pointer' to xml node (DO NOT MODIFY VALUE UNLESS YOU ARE CORE)

if __name__ == '__main__':
   try:
      import gui.MainWindow
   except ImportError, e:
      if e.message.find("wx") > 0:
         print "Cannot load module wx, do you have wxPython installed?"
      sys.exit(1)
   gui.MainWindow.main()
#   from xml.dom.ext import Print, PrettyPrint

#   Coreobj = Core()
#   Coreobj.addAccount('test', 1)
#   newTrans = Transaction(sourceAccount="test", destinationAccount="test", amount="7.53", notes="This is the note", date="04/22/08")
#   newTrans2 = Transaction(sourceAccount="test", destinationAccount="test", amount="13.95", notes="This is the note", date="04/22/08")
#   Coreobj.addTransaction(newTrans)
#   print Coreobj.getAccountBalance('test')
#   newTrans.amount = "5.23"
#   Coreobj.updateTransaction(newTrans)
#   Coreobj.addTransaction(newTrans2)
#   print Coreobj.getAccountBalance('test')
#   Coreobj.deleteTransaction(newTrans)
#   print Coreobj.getAccountBalance('test')
#   Coreobj.saveFile('testFile')
#   Coreobj.loadFile('core/Docs/cubudget.xml')
#   test = Coreobj.findAccount('bar')
#   name =  test.attributes.getNamedItem('name').value
#   print Coreobj.getAccountBalance(name)
#   Coreobj.addAccount('test', 1)
